Skip to content

fix(web): 멘토 채팅 이미지 대기 상태 개선#522

Merged
manNomi merged 4 commits into
mainfrom
fix/mentor-chat-image-pending-state
May 21, 2026
Merged

fix(web): 멘토 채팅 이미지 대기 상태 개선#522
manNomi merged 4 commits into
mainfrom
fix/mentor-chat-image-pending-state

Conversation

@manNomi
Copy link
Copy Markdown
Contributor

@manNomi manNomi commented May 21, 2026

Summary

  • sender는 이미지 전송 즉시 브라우저 메모리 blob preview를 optimistic 메시지로 표시
  • 서버 echo가 도착하면 optimistic preview를 실제 채팅 메시지에 흡수해 중복 표시를 방지
  • receiver는 썸네일 URL이 준비될 때까지 placeholder 대신 스피너를 보여주고 내부 이미지 health check를 수행
  • 기존 FallbackImage의 채팅 전용 retry 옵션은 제거하고, 채팅 화면에서만 대기 상태를 제어

Test

  • pnpm --filter @solid-connect/web lint:check
  • pnpm --filter @solid-connect/web typecheck
  • commit/push hook CI parity checks: lint:check, typecheck:ci, build

Notes

  • 기존 #521은 이미 merge되어, 최신 main 기준으로 후속 PR을 새로 생성했습니다.
  • 로컬 Node가 v23.10.0이라 repo 요구 Node 22.x 엔진 경고가 출력됩니다.
  • Next metadataBase/Sentry deprecation warning은 기존 빌드 경고입니다.

@manNomi manNomi requested review from enunsnv and wibaek as code owners May 21, 2026 07:06
@vercel
Copy link
Copy Markdown

vercel Bot commented May 21, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connection-web Ready Ready Preview, Comment May 21, 2026 7:34am
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connect-web-admin Skipped Skipped May 21, 2026 7:34am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 21, 2026

Review Change Stack

Warning

Rate limit exceeded

@manNomi has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 37 minutes before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 2dae416e-beda-4909-9521-927f9c7a0c91

📥 Commits

Reviewing files that changed from the base of the PR and between 54bea4f and 22edfcc.

📒 Files selected for processing (5)
  • apps/web/src/apis/chat/getChatMessages.ts
  • apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts
  • apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx
  • apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx
  • apps/web/src/lib/web-socket/useConnectWebSocket.ts

Walkthrough

이 PR은 채팅 시스템의 이미지 첨부 처리를 체계적으로 개선하는 아키텍처 개선입니다:

  1. 데이터 모델 확장
    ChatAttachment 타입에 previewUrlisOptimistic 필드를 추가하여 이미지 미리보기 추적 인프라 마련.

  2. Preview URL 재조정 엔진
    useChatListHandler 훅 내부에 이미지 URL 정규화 키 생성, imagePreviewByUrlRef 맵 기반 매칭, submittedMessages 변경 감지 effect를 통한 optimistic preview와 실제 preview의 자동 병합 및 정리 로직 구현.

  3. 헬스 체크 기반 렌더링
    ChatMessageBox에서 기존 재시도 상수 대신 useChatImageHealthCheck 훅으로 이미지 프로브 로드를 수행하고, ChatImage 컴포넌트로 캡슐화하여 렌더링 전 이미지 가용성 확인.

  4. End-to-End 전송 흐름
    ChatContent의 이미지 전송 시 preview URL 생성 → 업로드 → sendImageMessage 호출 → 실패 시 cleanup 하는 통합 파이프라인.

  5. 레거시 재시도 제거
    FallbackImage에서 복잡한 재시도 타이머 로직을 걷어내고 실패 시 즉시 fallback으로 전환하는 간단한 방식으로 단순화.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

  • wibaek
  • enunsnv
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Description check ❓ Inconclusive PR 설명이 작업 내용과 테스트 항목은 잘 작성되었으나, 필수 섹션인 '관련 이슈' 및 '특이 사항' 섹션이 누락되어 있습니다. 관련 이슈(resolves: #번호)와 특이 사항을 명확히 추가하거나, 기존 설명의 Notes 섹션을 '특이 사항'으로 재구성해 주세요.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 핵심 변경 사항인 '멘토 채팅 이미지 대기 상태 개선'을 명확하게 요약하고 있으며, 파일 목록이나 모호한 표현 없이 간결하고 구체적입니다.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/mentor-chat-image-pending-state

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@apps/web/src/app/mentor/chat/`[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts:
- Around line 159-165: When chatId changes the hook currently resets
prevChatIdRef, hasInitialAutoScrolledRef, and prevMessageCountRef but does not
clear preview state, causing leftover blob URLs and image mappings to leak into
memory and future rooms; inside the same useEffect that checks prevChatIdRef and
sets those refs, iterate objectUrlsRef.current and call URL.revokeObjectURL for
each stored URL then set objectUrlsRef.current = new Map() (or clear()) and set
imagePreviewByUrlRef.current = new Map() (or clear()) so both the blob URLs are
revoked and the preview mapping is emptied when chatId changes; reference the
useEffect in useChatListHandler.ts and the refs objectUrlsRef and
imagePreviewByUrlRef when applying this change.

In
`@apps/web/src/app/mentor/chat/`[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx:
- Around line 74-80: The current logic flips setIsReady(true) and
setIsFailed(true) when attempt >= CHAT_IMAGE_HEALTH_CHECK_LIMIT, which forces
rendering of FallbackImage permanently and never re-probes CDN updates; change
this so that hitting the fast retry limit marks the fast probe as failed but
does NOT mark the message permanently ready. Instead, keep setIsReady(false) (or
clear the ready flag) and schedule a slower, longer-interval poll (or restart
checkImage on a longer timeout) so the background probe continues (e.g., after
CHAT_IMAGE_HEALTH_CHECK_LIMIT is reached use a slower interval or a separate
slow retry loop instead of terminating), and mirror the same change for the
analogous block around lines 105-119; reference functions/vars: checkImage,
CHAT_IMAGE_HEALTH_CHECK_LIMIT, CHAT_IMAGE_HEALTH_CHECK_INTERVAL_MS, setIsReady,
setIsFailed to locate and update the logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 090c020b-eb07-4d22-a415-f4461c0bd28a

📥 Commits

Reviewing files that changed from the base of the PR and between 126f730 and 54bea4f.

📒 Files selected for processing (5)
  • apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_hooks/useChatListHandler.ts
  • apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/_ui/ChatMessageBox/index.tsx
  • apps/web/src/app/mentor/chat/[chatId]/_ui/ChatContent/index.tsx
  • apps/web/src/components/ui/FallbackImage.tsx
  • apps/web/src/types/chat.ts

@vercel vercel Bot temporarily deployed to Preview – solid-connect-web-admin May 21, 2026 07:29 Inactive
@manNomi manNomi merged commit c2da903 into main May 21, 2026
9 of 10 checks passed
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 22edfcc0aa

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines 58 to 60
if (!hasError && resolvedSrc !== fallbackSrc) {
setFailedSource(sourceKey);

if (canRetry) {
if (retryTimeoutRef.current) {
clearTimeout(retryTimeoutRef.current);
}

retryTimeoutRef.current = setTimeout(() => {
setRetryAttempt((prev) => prev + 1);
setFailedSource((current) => (current === sourceKey ? null : current));
retryTimeoutRef.current = null;
}, normalizedRetryDelayMs);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset failed source when image src changes

After an image load error, failedSource is set and never cleared unless the component unmounts. Because hasError is derived from failedSource === sourceKey, returning to the same src later (e.g., transient CDN failure, then switching away and back) will immediately render fallback and never retry that URL in this component instance. The previous implementation reset failure state on source changes, so this is a regression in recoverability.

Useful? React with 👍 / 👎.

Comment on lines +75 to +79
const retryDelayMs =
attempt >= CHAT_IMAGE_HEALTH_CHECK_LIMIT
? CHAT_IMAGE_HEALTH_CHECK_SLOW_INTERVAL_MS
: CHAT_IMAGE_HEALTH_CHECK_INTERVAL_MS;
timeoutId = setTimeout(checkImage, retryDelayMs);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Stop health-check polling after repeated image failures

The chat image health check schedules another checkImage on every onerror with no terminal condition, so a permanently invalid/forbidden URL will be requested indefinitely (every 5s after the limit). In long-lived chat sessions this creates unbounded background traffic and leaves the UI stuck on a spinner instead of falling back to a stable failure state.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant